package checker

import (
	"kumachan/interpreter/lang/ast"
	"kumachan/interpreter/compiler/checker/typsys"
	"kumachan/interpreter/compiler/checker/checked"
	"kumachan/interpreter/lang/common/source"
)


type quickLambda func(arg ast.Expr)(ast.Expr)

func checkLambda(L ast.Lambda, inject ([] *checked.LocalBinding)) ExprChecker {
	return makeExprCheckerWithLocalScope(L.Location, func(cc *checkContextWithLocalScope) checkResult {
		var io, e = cc.getExpectedLambda(cc.expected, cc.location)
		if e != nil { return cc.throw(e) }
		if inject != nil {
			for _, item := range inject {
				cc.injectLocalBinding(item)
			}
		}
		var self_ref *checked.LocalBinding
		if ast.Id2String(L.Self) != "" {
			var self_pattern = ast.VariousPattern {
				Node:    L.Node,
				Pattern: ast.PatternTrivial {
					Node: L.Node,
					Name: L.Self,
				},
			}
			var self_t, expect_certain = cc.getCertainType(cc.expected)
			if !(expect_certain) {
				return cc.error(
					E_ExplicitTypeRequired {})
			}
			var info, err = cc.productPatternMatch(self_pattern, self_t)
			if err != nil { panic("something went wrong") }
			if len(info) != 1 { panic("something went wrong") }
			self_ref = info[0].Binding
		}
		var in_t = io.Input
		var out_t = io.Output
		var in_pattern, err1 = cc.productPatternMatch(L.Input, in_t)
		if err1 != nil { return cc.propagate(err1) }
		var out_expr, err2 = cc.checkChildExpr(out_t, L.Output)
		if err2 != nil { return cc.propagate(err2) }
		var lambda_t = &typsys.NestedType { Content:
			typsys.Lambda {
					Input:  in_t,
					Output: out_expr.Type,
				} }
		return cc.assign(
			lambda_t,
			checked.Lambda {
				In:   in_pattern,
				Out:  out_expr,
				Self: self_ref,
			})
	})
}

func checkQuickLambda(node ast.Node, f quickLambda) ExprChecker {
	return makeExprChecker(node.Location, func(cc *checkContext) checkResult {
		var io, e = cc.getExpectedLambda(cc.expected, cc.location)
		if e != nil { return cc.throw(e) }
		var in_t = io.Input
		var out_t = io.Output
		var arg_node = ast.WrapTermAsExpr(ast.VariousTerm {
			Node: node,
			Term: CheckedExprTerm { Expr: &checked.Expr {
				Type:    in_t,
				Info:    checked.ExprInfoFrom(cc.location),
				Content: checked.Argument {},
			} },
		})
		var out_node = f(arg_node)
		var out_expr, err = cc.checkChildExpr(out_t, out_node)
		if err != nil { return cc.propagate(err) }
		var lambda_t = &typsys.NestedType { Content:
			typsys.Lambda {
				Input:  in_t,
				Output: out_expr.Type,
			} }
		var in = [] checked.ProductPatternItemInfo { {
			Binding: nil,
			Index1:  0,
		} }
		return cc.assign(
			lambda_t,
			checked.Lambda {
				In:  in,
				Out: out_expr,
			})
	})
}

func checkBlock(B ast.Block) ExprChecker {
	return makeExprCheckerWithLocalScope(B.Location, func(cc *checkContextWithLocalScope) checkResult {
		var cons_ctx = cc.typeConsContext()
		if len(B.Bindings) == 0 { panic("something went wrong") }
		var let_list = make([] checked.Let, len(B.Bindings))
		for i, binding := range B.Bindings {
			var declared_t, err = (func() (typsys.Type, *source.Error) {
				var type_node, type_declared = binding.Type.(ast.VariousType)
				if type_declared {
					return newType(type_node, cons_ctx)
				} else {
					return nil, nil
				}
			})()
			if err != nil { return cc.propagate(err) }
			var pattern_node = binding.Pattern
			var value_node = binding.Value
			var expr, err1 = cc.checkChildExpr(declared_t, value_node)
			if err1 != nil { return cc.propagate(err1) }
			var t = (func() typsys.Type {
				if declared_t == nil {
					return expr.Type
				} else {
					return declared_t
				}
			})()
			var pattern, err2 = cc.productPatternMatch(pattern_node, t)
			if err2 != nil { return cc.propagate(err2) }
			let_list[i] = checked.Let {
				Pattern: pattern,
				Input:   expr,
			}
		}
		return cc.assignFinalNode(B.Return, func(ret *checked.Expr) checked.ExprContent {
			return checked.Block {
				LetList: let_list,
				Return:  ret,
			}
		})
	})
}

func checkBranch(t typsys.Type, opt_in ast.MaybePattern, out ast.Expr, loc source.Location) ExprChecker {
	return makeExprCheckerWithLocalScope(loc, func(cc *checkContextWithLocalScope) checkResult {
		var in, has_input = opt_in.(ast.VariousPattern)
		var pattern, err = (func() (checked.ProductPatternInfo, *source.Error) {
			if has_input {
				var pattern, err1 = cc.productPatternMatch(in, t)
				if err1 != nil { return nil, err1 }
				return pattern, nil
			} else {
				return checked.ProductPatternInfo(nil), nil
			}
		})()
		if err != nil { return cc.propagate(err) }
		return cc.assignFinalNode(out, func(out_expr *checked.Expr) checked.ExprContent {
			return checked.Branch { Lambda:
				checked.Lambda {
					In:  pattern,
					Out: out_expr,
				} }
		})
	})
}


func getLambda(t typsys.Type) (typsys.Lambda, bool) {
	var nested, is_nested = t.(*typsys.NestedType)
	if !(is_nested) { return typsys.Lambda {}, false }
	var lambda, is_tuple = nested.Content.(typsys.Lambda)
	return lambda, is_tuple
}
func unboxLambda(t typsys.Type, mod string) (typsys.Lambda, bool) {
	var lambda, is_lambda = getLambda(t)
	if is_lambda {
		return lambda, true
	} else {
		var inner, _, exists = typsys.Unbox(t, mod)
		if exists {
			return unboxLambda(inner, mod)
		} else {
			return typsys.Lambda {}, false
		}
	}
}
func obtainIsoLambda(t typsys.Type, mod string) (typsys.Lambda, bool) {
	var lambda, is_lambda = getLambda(t)
	if is_lambda {
		return lambda, true
	} else {
		var inner, box, is_box = typsys.Unbox(t, mod)
		if is_box && box.BoxKind == typsys.Isomorphic {
			return obtainIsoLambda(inner, mod)
		} else {
			return typsys.Lambda {}, false
		}
	}
}

